﻿using System;
using System.Collections.Generic;

namespace AppMain
{
    public struct Vec3 : IEquatable<Vec3>
    {
        // *Undocumented*
        public const float kEpsilon = 0.00001F;
        // *Undocumented*
        public const float kEpsilonNormalSqrt = 1e-15F;
        public Vec3(float x, float y, float z) { this.x = x; this.y = y; this.z = z; }
        public Vec3(float x, float y) { this.x = x; this.y = y; z = 0F; }

        public float x;
        public float y;
        public float z;

        public static float Dot(Vec3 lhs, Vec3 rhs) { return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z; }

        public static Vec3 Cross(Vec3 lhs, Vec3 rhs)
        {
            return new Vec3(
                lhs.y * rhs.z - lhs.z * rhs.y,
                lhs.z * rhs.x - lhs.x * rhs.z,
                lhs.x * rhs.y - lhs.y * rhs.x);
        }
        public override bool Equals(object other)
        {
            if (!(other is Vec3)) return false;

            return Equals((Vec3)other);
        }

        public bool Equals(Vec3 other)
        {
            return x == other.x && y == other.y && z == other.z;
        }
        public override int GetHashCode()
        {
            return x.GetHashCode() ^ (y.GetHashCode() << 2) ^ (z.GetHashCode() >> 2);
        }

        public static float Magnitude(Vec3 vector) { return (float)Math.Sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z); }
        public static Vec3 Normalize(Vec3 value)
        {
            float mag = Magnitude(value);
            if (mag > kEpsilon)
                return value / mag;
            else
                return zero;
        }

        // Makes this vector have a ::ref::magnitude of 1.
        public void Normalize()
        {
            float mag = Magnitude(this);
            if (mag > kEpsilon)
                this = this / mag;
            else
                this = zero;
        }
        public Vec3 normalized { get { return Vec3.Normalize(this); } }

        static readonly Vec3 zeroVector = new Vec3(0F, 0F, 0F);
        static readonly Vec3 oneVector = new Vec3(1F, 1F, 1F);
        static readonly Vec3 upVector = new Vec3(0F, 1F, 0F);
        static readonly Vec3 downVector = new Vec3(0F, -1F, 0F);
        static readonly Vec3 leftVector = new Vec3(-1F, 0F, 0F);
        static readonly Vec3 rightVector = new Vec3(1F, 0F, 0F);
        static readonly Vec3 forwardVector = new Vec3(0F, 0F, 1F);
        static readonly Vec3 backVector = new Vec3(0F, 0F, -1F);

        // Shorthand for writing @@Vector3(0, 0, 0)@@
        public static Vec3 zero { get { return zeroVector; } }
        // Shorthand for writing @@Vector3(1, 1, 1)@@
        public static Vec3 one { get { return oneVector; } }
        // Shorthand for writing @@Vector3(0, 0, 1)@@
        public static Vec3 forward { get { return forwardVector; } }
        public static Vec3 back { get { return backVector; } }
        // Shorthand for writing @@Vector3(0, 1, 0)@@
        public static Vec3 up { get { return upVector; } }
        public static Vec3 down { get { return downVector; } }
        public static Vec3 left { get { return leftVector; } }
        // Shorthand for writing @@Vector3(1, 0, 0)@@
        public static Vec3 right { get { return rightVector; } }

        // Adds two vectors.
        public static Vec3 operator +(Vec3 a, Vec3 b) { return new Vec3(a.x + b.x, a.y + b.y, a.z + b.z); }
        // Subtracts one vector from another.
        public static Vec3 operator -(Vec3 a, Vec3 b) { return new Vec3(a.x - b.x, a.y - b.y, a.z - b.z); }
        // Negates a vector.
        public static Vec3 operator -(Vec3 a) { return new Vec3(-a.x, -a.y, -a.z); }
        // Multiplies a vector by a number.
        public static Vec3 operator *(Vec3 a, float d) { return new Vec3(a.x * d, a.y * d, a.z * d); }
        // Multiplies a vector by a number.
        public static Vec3 operator *(float d, Vec3 a) { return new Vec3(a.x * d, a.y * d, a.z * d); }
        // Divides a vector by a number.
        public static Vec3 operator /(Vec3 a, float d) { return new Vec3(a.x / d, a.y / d, a.z / d); }
        // Returns true if the vectors are equal.
        public static bool operator ==(Vec3 lhs, Vec3 rhs)
        {
            // Returns false in the presence of NaN values.
            float diff_x = lhs.x - rhs.x;
            float diff_y = lhs.y - rhs.y;
            float diff_z = lhs.z - rhs.z;
            float sqrmag = diff_x * diff_x + diff_y * diff_y + diff_z * diff_z;
            return sqrmag < kEpsilon * kEpsilon;
        }

        // Returns true if vectors are different.
        public static bool operator !=(Vec3 lhs, Vec3 rhs)
        {
            // Returns true in the presence of NaN values.
            return !(lhs == rhs);
        }

        public override string ToString()
        {
            return string.Format("({0}, {1}, {2})", x, y, z);
        }
    }

}
